1
2
3
4 package edu.jiangxin.apktoolbox.android.i18n;
5
6 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
7 import edu.jiangxin.apktoolbox.swing.extend.listener.SelectDirectoryListener;
8 import edu.jiangxin.apktoolbox.utils.Constants;
9 import org.apache.commons.collections4.map.HashedMap;
10 import org.apache.commons.io.FileUtils;
11 import org.apache.commons.lang3.StringUtils;
12 import org.jdom2.Document;
13 import org.jdom2.Element;
14 import org.jdom2.JDOMException;
15 import org.jdom2.input.SAXBuilder;
16 import org.jdom2.output.Format;
17 import org.jdom2.output.XMLOutputter;
18
19 import javax.swing.*;
20 import java.awt.*;
21 import java.io.*;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.Map;
26
27
28
29
30
31
32 public class I18nAddPanel extends EasyPanel {
33
34 @Serial
35 private static final long serialVersionUID = 1L;
36
37 private static final String CHARSET = "UTF-8";
38
39 private static final boolean REMOVE_LAST_LF_OPEN = true;
40
41 private static final Map<String, String> replace = new HashedMap<>();
42
43 private JTextField srcTextField;
44
45 private JTextField targetTextField;
46
47 private JTextField itemTextField;
48
49 private int operationCount = 0;
50
51 static {
52 replace.put(""", "jiangxin001");
53 replace.put(" ", "jiangxin002");
54 }
55
56 public I18nAddPanel() throws HeadlessException {
57 super();
58 }
59
60 @Override
61 public void initUI() {
62 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
63 setLayout(boxLayout);
64
65 createSourcePanel();
66 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
67 createTargetPanel();
68 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
69 createItemPanel();
70 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
71 createOperationPanel();
72 }
73
74 private void createOperationPanel() {
75 JPanel operationPanel = new JPanel();
76 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
77 add(operationPanel);
78
79 JButton addButton = new JButton(bundle.getString("android.i18n.add.title"));
80 addButton.addActionListener(e -> {
81 String srcPath = checkAndGetDirContent(srcTextField, "android.i18n.add.src.dir", "Source directory is invalid");
82 if (srcPath == null) {
83 return;
84 }
85
86 String targetPath = checkAndGetDirContent(targetTextField, "android.i18n.add.target.dir", "Target directory is invalid");
87 if (targetPath == null) {
88 return;
89 }
90
91 String itemStr = checkAndGetStringContent(itemTextField, "android.i18n.add.items", "Items is empty");
92 if (itemStr == null) {
93 return;
94 }
95
96 List<String> items = new ArrayList<>(Arrays.asList(itemStr.split(";")));
97 operationCount = 0;
98
99 for (String item : items) {
100 int ret = innerProcessor(srcPath, targetPath, item);
101 if (ret != 0) {
102 Toolkit.getDefaultToolkit().beep();
103 JOptionPane.showMessageDialog(I18nAddPanel.this, "Failed, please see the log", "ERROR",
104 JOptionPane.ERROR_MESSAGE);
105 return;
106 }
107 }
108 String message = String.format("Success: items: %d", operationCount);
109 JOptionPane.showMessageDialog(I18nAddPanel.this, message, "INFO", JOptionPane.INFORMATION_MESSAGE);
110 });
111
112 operationPanel.add(addButton);
113 }
114
115 private void createItemPanel() {
116 JPanel itemPanel = new JPanel();
117 itemPanel.setLayout(new BoxLayout(itemPanel, BoxLayout.X_AXIS));
118 add(itemPanel);
119
120 itemTextField = new JTextField();
121 itemTextField.setText(conf.getString("android.i18n.add.items"));
122
123 JLabel itemLabel = new JLabel("Items");
124
125 itemPanel.add(itemTextField);
126 itemPanel.add(Box.createHorizontalGlue());
127 itemPanel.add(itemLabel);
128 }
129
130 private void createTargetPanel() {
131 JPanel targetPanel = new JPanel();
132 targetPanel.setLayout(new BoxLayout(targetPanel, BoxLayout.X_AXIS));
133 add(targetPanel);
134
135 targetTextField = new JTextField();
136 targetTextField.setText(conf.getString("android.i18n.add.target.dir"));
137
138 JButton targetButton = new JButton("Save Directory");
139 targetButton.addActionListener(new SelectDirectoryListener("save to", targetTextField));
140
141 targetPanel.add(targetTextField);
142 targetPanel.add(Box.createHorizontalGlue());
143 targetPanel.add(targetButton);
144 }
145
146 private void createSourcePanel() {
147 JPanel sourcePanel = new JPanel();
148 sourcePanel.setLayout(new BoxLayout(sourcePanel, BoxLayout.X_AXIS));
149 add(sourcePanel);
150
151 srcTextField = new JTextField();
152 srcTextField.setText(conf.getString("android.i18n.add.src.dir"));
153
154 JButton srcButton = new JButton("Source Directory");
155 srcButton.addActionListener(new SelectDirectoryListener("select a directory", srcTextField));
156
157 sourcePanel.add(srcTextField);
158 sourcePanel.add(Box.createHorizontalGlue());
159 sourcePanel.add(srcButton);
160 }
161
162 private int innerProcessor(String sourceBaseStr, String targetBaseStr, String itemName) {
163 if (StringUtils.isAnyEmpty(sourceBaseStr, targetBaseStr, itemName)) {
164 logger.error("params are invalid: sourceBaseStr: {}, targetBaseStr: {}, itemName: {}", sourceBaseStr, targetBaseStr, itemName);
165 return -1;
166 }
167 File sourceBaseFile = new File(sourceBaseStr);
168 File targetBaseFile = new File(targetBaseStr);
169 int count = 0;
170
171 File[] sourceParentFiles = sourceBaseFile.listFiles(new FileFilter() {
172 @Override
173 public boolean accept(File pathname) {
174 return pathname.getName().startsWith("values");
175 }
176 });
177 if (sourceParentFiles == null) {
178 logger.error("sourceParentFiles is null");
179 return -1;
180 }
181 for (File sourceParentFile : sourceParentFiles) {
182 File sourceFile = new File(sourceParentFile, "strings.xml");
183
184 Element sourceElement = getSourceElement(sourceFile, itemName);
185 if (sourceElement == null) {
186 logger.warn("sourceElement is null: {}", sourceFile);
187 continue;
188 }
189
190 File targetFile = new File(new File(targetBaseFile, sourceParentFile.getName()), "strings.xml");
191 if (!targetFile.exists()) {
192 logger.warn("targetFile does not exist: {}", sourceFile);
193 continue;
194 }
195 try {
196 preProcess(targetFile);
197 } catch (IOException e) {
198 logger.error("preProcess failed.", e);
199 return -1;
200 }
201 boolean res = setTargetElement(targetFile, sourceElement, itemName);
202 if (!res) {
203 logger.error("setTargetElement failed.");
204 return -1;
205 }
206 try {
207 postProcess(targetFile);
208 } catch (IOException e) {
209 logger.error("postProcess failed.", e);
210 return -1;
211 }
212 logger.info("count: {}, in path: {}, out path: {}", ++count, sourceFile, targetFile);
213 }
214 operationCount += count;
215 logger.info("finish one cycle");
216 return 0;
217 }
218
219 private Element getSourceElement(File sourceFile, String itemName) {
220 if (!sourceFile.exists()) {
221 logger.warn("sourceFile does not exist: {}", sourceFile);
222 return null;
223 }
224 SAXBuilder builder = new SAXBuilder();
225 Document sourceDoc = null;
226 try (InputStream in = new FileInputStream(sourceFile)) {
227 sourceDoc = builder.build(in);
228 logger.info("build source document: {}", sourceFile);
229 } catch (JDOMException | IOException e) {
230 logger.error("build source document failed: {}", sourceFile);
231 return null;
232 }
233 if (sourceDoc == null) {
234 logger.error("sourceDoc is null");
235 return null;
236 }
237 Element sourceElement = null;
238 for (Element sourceChild : sourceDoc.getRootElement().getChildren()) {
239 String sourceValue = sourceChild.getAttributeValue("name");
240 if (sourceValue != null && sourceValue.equals(itemName)) {
241 sourceElement = sourceChild.clone();
242 break;
243 }
244 }
245 return sourceElement;
246 }
247
248 private boolean setTargetElement(File targetFile, Element sourceElement, String itemName) {
249 SAXBuilder builder = new SAXBuilder();
250 Document targetDoc;
251 try {
252 targetDoc = builder.build(targetFile);
253 logger.info("build target document: {}", targetFile);
254 } catch (JDOMException | IOException e) {
255 logger.error("build target document failed: {}", targetFile);
256 return false;
257 }
258 Element targetRoot = targetDoc.getRootElement();
259 boolean isFinished = false;
260 for (Element targetChild : targetRoot.getChildren()) {
261 String targetValue = targetChild.getAttributeValue("name");
262 if (targetValue != null && targetValue.equals(itemName)) {
263 targetChild.setText(sourceElement.getText());
264 isFinished = true;
265 break;
266 }
267 }
268 if (!isFinished) {
269 targetRoot.addContent(" ");
270 targetRoot.addContent(sourceElement);
271 targetRoot.addContent("\n");
272 }
273 XMLOutputter out = new XMLOutputter();
274 Format format = Format.getRawFormat();
275 format.setEncoding("UTF-8");
276 format.setLineSeparator("\n");
277 out.setFormat(format);
278 OutputStream os = null;
279 try {
280 os = new FileOutputStream(targetFile);
281 out.output(targetDoc, os);
282 } catch (IOException e) {
283 logger.error("output fail", e);
284 return false;
285 } finally {
286 if (os != null) {
287 try {
288 os.close();
289 } catch (IOException e) {
290 logger.error("close output stream exception", e);
291 }
292 }
293 }
294 return true;
295 }
296
297 private static void preProcess(File file) throws IOException {
298 String content = FileUtils.readFileToString(file, CHARSET);
299 for (Map.Entry<String, String> entry : replace.entrySet()) {
300 content = content.replaceAll(entry.getKey(), entry.getValue());
301 }
302 FileUtils.writeStringToFile(file, content, CHARSET);
303 }
304
305 private static void postProcess(File file) throws IOException {
306 String content = FileUtils.readFileToString(file, CHARSET);
307 for (Map.Entry<String, String> entry : replace.entrySet()) {
308 content = content.replaceAll(entry.getValue(), entry.getKey());
309 }
310 if (REMOVE_LAST_LF_OPEN) {
311 content = StringUtils.removeEnd(content, "\n");
312 }
313 FileUtils.writeStringToFile(file, content, CHARSET);
314 }
315
316 }